package com.jin.webvideo.base;

import com.jin.webvideo.base.util.CollectionsUtil;
import com.jin.webvideo.base.util.SystemInfo;
import com.jin.webvideo.entity.ForbidRecord;
import com.jin.webvideo.entity.SessionStore;
import com.jin.webvideo.entity.User;
import com.jin.webvideo.exception.DataException;
import com.jin.webvideo.service.AccessServer;
import com.jin.webvideo.service.ConfigService;
import com.jin.webvideo.service.UserService;
import com.jin.webvideo.util.consts.ConfigConst;
import com.jin.webvideo.util.consts.CookieConst;
import com.jin.webvideo.util.base.DateUtil;
import com.jin.webvideo.util.base.StringUtil;
import com.jin.webvideo.util.sys.EncryptUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 登录/登出处理器
 */

@Component
public class LoginHandler implements HandlerInterceptor {
    private String checkCodePage = "";
    // 登录页面的地址
    private String loginPage = "";
    // 登录提交的地址
    private String loginAction = "";
    // 登录成功的默认转跳地址
    private String loginSuccess = "";
    // 登出提交的地址
    private String logoutAction = "";
    // 登出后的默认页面
    private String logoutPath = "";
    // 账号字段
    private String accountField = "account";
    // 密码字段
    private String passwordField = "password";
    // 验证码字段
    private String chkCodeField = "captcha";

    @Autowired
    private ConfigService configService;
    @Autowired
    private UserService userService;
    @Autowired
    private AccessServer accessServer;

    public void login(String path) {
        this.loginPage = path;
    }
    public void vCode(String vCodePage) {
        this.checkCodePage = vCodePage;
    }
    public void logoutAction(String path) {
        this.logoutAction = path;
    }

    public void logoutSuccess(String path) {
        this.logoutPath = path;
    }

    public void loginSuccess(String path) {
        this.loginSuccess = path;
    }

    public void loginAction(String path, String accountField, String passwordField, String chkCodeField) {
        this.loginAction = path;
        this.accountField = StringUtil.getDefault(accountField, this.accountField);
        this.passwordField = StringUtil.getDefault(passwordField, this.passwordField);
        this.chkCodeField = StringUtil.getDefault(chkCodeField, this.chkCodeField);
    }

    public boolean login(HttpServletRequest request, HttpServletResponse response, String account, String password) {
        return login(request, response, account, password, true, false);
    }

    /**
     *
     * @param request
     * @param response
     * @return true 已处理不需要继续处理， false 需要继续处理
     */
    public boolean loginHandle(HttpServletRequest request, HttpServletResponse response) {
        try {
            String uri = request.getRequestURI();
            String method = request.getMethod();
            // 当前请求为登陆页面
            if (uri.equals(loginPage) && "GET".equalsIgnoreCase(method)) {
                HttpSession session = request.getSession();
                // 设置登录成功的重定向页面，需要页面引用策略<meta name="referrer" content="same-origin"/> 配合
                if (null == session.getAttribute(HttpHeaders.REFERER)) {
                    session.setAttribute(HttpHeaders.REFERER, request.getHeader(HttpHeaders.REFERER));
                }
                return false;
            }
            // 当前请求为登录操作
            if (uri.equals(loginAction) && "POST".equalsIgnoreCase(method)) {
                // 调用登陆方法
                boolean login = login(request, response, request.getParameter(accountField), request.getParameter(passwordField), false, true);
                // 处理登录成功的重定向页面
                if (login) {
                    redirect(request, response);
                }
                else {
                    response.sendRedirect(loginPage);
                }
                return true;
            }
            // 当前请求为登出操作
            if (uri.equals(logoutAction)) {
                // 处理登出
                logout(request, response);
                return true;
            }
            if (uri.equals(checkCodePage)) {
                createCode(request, response);
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 创建验证码
     * @param request
     * @param response
     */
    private void createCode(HttpServletRequest request, HttpServletResponse response) {
        SessionStore store = getSessionStore(request);
        store.setAttribute(SessionStore.VerificationCode, null);
        /*ServletOutputStream servletOutputStream = response.getOutputStream();
        servletOutputStream.write();
        servletOutputStream.flush();
        servletOutputStream.close();*/

    }

    /**
     * 处理登录成功后的转跳
     * @param request
     * @param response
     * @throws IOException
     */
    private void redirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String referer = (String) request.getSession().getAttribute(HttpHeaders.REFERER);
        request.getSession().removeAttribute(HttpHeaders.REFERER);
        if (!StringUtil.isEmpty(referer)) {
            response.sendRedirect(referer);
            return;
        }
        response.sendRedirect(loginSuccess);
    }

    /**
     * 退出登录
     * @param request
     * @param response
     * @throws IOException
     */
    private void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        // 清除Cookies
        response.addCookie(LoginHandler.createCookie(CookieConst.AUTO_LOGIN, null, 0));
        response.addCookie(LoginHandler.createCookie(CookieConst.ACCOUNT, null, 0));
        response.addCookie(LoginHandler.createCookie(CookieConst.PASSWORD, null, 0));
        response.addCookie(LoginHandler.createCookie(CookieConst.LOGIN_TIME, null, 0));
        SessionStore store = getSessionStore(session);
        if (store != null){
            store.setUser(null);
        }
        // 退出后返回的页面
        String referer = request.getHeader(HttpHeaders.REFERER);
        if (!StringUtil.isEmpty(referer)) {
            response.sendRedirect(referer);
            return;
        }
        response.sendRedirect(logoutPath);
    }

    /**
     * 登录
     * @param request
     * @param response
     * @param account 账号
     * @param password 密码
     * @param isCiphertext 密码是否为密文
     * @param writeCookie 是否需要将信息写入cookie
     * @return
     */
    private boolean login(HttpServletRequest request, HttpServletResponse response, String account, String password, boolean isCiphertext, boolean writeCookie) {
        SessionStore store = getSessionStore(request);

        // 验证码验证，当是密文时，此字段必为null
        String code = request.getParameter(chkCodeField);
        String vcode = SessionStore.getAttribute(store, SessionStore.VerificationCode);
        if (!StringUtil.equals(code, vcode)) {
            store.removeAttribute(SessionStore.VerificationCode);
            return false;
        }

        Map<String,String> config = configService.configMapByGroupId(ConfigConst.PASSWORD_CONFIG);
        String key = config.get(ConfigConst.ATTR_CONFIG_KEY);
        // 如果password是密文，则解密
        if (isCiphertext) {
            password = LoginHandler.decrypt(key, account, password);
        }
        // 调用登录方法
        User user = userService.login(account, password);
        if (user != null){
            forbidRoles(user);
            // 登录成功设置session
            store.setUser(user);

            // 如果来自自动登录则不设置Cookie, 如果未设置自动登录也不记入
            if (writeCookie) {
                String expiration = config.get(ConfigConst.ATTR_CONFIG_COOKIE_EXPIRATION);
                int expira = StringUtil.getNumber(expiration, ConfigConst.PASSWORDCONFIG_COOKIE_EXPIRATION_DEFAULT);
                response.addCookie(LoginHandler.createCookie(CookieConst.AUTO_LOGIN, "true", expira));
                response.addCookie(LoginHandler.createCookie(CookieConst.ACCOUNT, account, expira));
                response.addCookie(LoginHandler.createCookie(CookieConst.LOGIN_TIME, DateUtil.format(new Date(),DateUtil.FORMAT_DEFAULT), expira));
                password = LoginHandler.encrypt(key, account, password);
                response.addCookie(LoginHandler.createCookie(CookieConst.PASSWORD, password, expira));
            }
            return true;
        }
        return false;
    }

    /**
     * 禁用角色
     * @param user
     */
    private void forbidRoles(User user) {
        // 获取角色禁用记录
        List<ForbidRecord> forbidRoles = accessServer.findRecordByUser(user.getAccount(), SystemInfo.nowSecond(), ForbidRecord.FORBID_TYPE_USER_ROLE);
        List<String> roles = CollectionsUtil.convert(forbidRoles, (r) -> r.getVal());
        if (CollectionsUtil.isEmpty(user.getRoles())) {
            return;
        }
        Iterator<User.Role> iterator = user.getRoles().iterator();
        while (iterator.hasNext()) {
            String role = iterator.next().getName();
            // 如果存在禁用的角色，从角色列表中删除
            for (String roleName : roles) {
                if (StringUtil.equals(roleName, role)) {
                    iterator.remove();
                    break;
                }
            }
        }
    }

    /**
     * 创建Cookie
     * @param key
     * @param value
     * @param maxAge
     * @return
     */
    public static Cookie createCookie(String key, String value, int maxAge) {
        Cookie cookie = new Cookie(key, value);
        cookie.setMaxAge(maxAge);
        cookie.setPath("/");
        return cookie;
    }

    /**
     * 获取已登录账号的 账号id
     * @param session
     * @return
     */
    public static String getAccount(HttpSession session) {
        SessionStore store = getSessionStore(session);
        User user = store.getUser();
        if (user == null) {
            return null;
        }
        return user.getAccount();
    }

    /**
     * 加密password
     * @param key
     * @param account
     * @param password
     * @return
     */
    public static String encrypt(String key, String account, String password) {
        if (StringUtil.isEmpty(password)) {
            return "";
        }
        byte[] iv = EncryptUtil.md5(account.getBytes(StandardCharsets.UTF_8));
        byte[] k = EncryptUtil.md5(key.getBytes(StandardCharsets.UTF_8));
        byte[] bs = EncryptUtil.encryptAESCBCPKCS5(password, iv, k);
        return EncryptUtil.toHex(bs);
    }

    /**
     * 解密password
     * @param key
     * @param account
     * @param password
     * @return
     */
    public static String decrypt(String key, String account, String password) {
        if (StringUtil.isEmpty(password)) {
            return "";
        }
        byte[] p = EncryptUtil.toByte(password);
        byte[] iv = EncryptUtil.md5(account.getBytes(StandardCharsets.UTF_8));
        byte[] k = EncryptUtil.md5(key.getBytes(StandardCharsets.UTF_8));
        byte[] bs = EncryptUtil.decryptAESCBCPKCS5(p, iv, k);
        return new String(bs);
    }
    public static SessionStore getSessionStore(HttpServletRequest request) {
        return getSessionStore(request.getSession());
    }
    public static SessionStore getSessionStore(HttpSession session) {
        return (SessionStore) session.getAttribute(CookieConst.SESSION_KEY);
    }

    /**
     * 是否已登录
     * @param session
     * @return
     */
    public static boolean loggedIn(HttpSession session) {
        SessionStore store = getSessionStore(session);
        if (store == null) {
            return false;
        }
        return store.getUser() != null;
    }

    /**
     * 断言用户是否已登录
     * @param session
     * @return
     * @throws DataException
     */
    public static String assetGetLogin(HttpSession session) throws DataException {
        SessionStore store = getSessionStore(session);
        User account = store.getUser();
        if (account == null){
            throw new DataException("您尚未登录或登录已失效");
        }
        return account.getAccount();
    }
}
